package cn.android666.net.util

import android.os.Handler
import android.os.Looper
import android.text.TextUtils
import com.google.gson.GsonBuilder
import com.google.gson.internal.GsonTypes
import okhttp3.Call
import okhttp3.Callback
import okhttp3.FormBody
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.io.InterruptedIOException
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.net.SocketTimeoutException
import java.util.concurrent.TimeUnit

/**
 * 网络请求工具，为何取名为Nice，这是为了方便平时输入，因为基本上系统Api很少有以Nice开头的，输入Nice后基本上是唯一的。如果输入Network，则会有很多Network开头的，如果你需要的不在第一行就很麻烦。
 */
@Suppress("unused")
object Nice {

    /**
     * 用于解析json的类，disableHtmlEscaping()禁止对字符串进行转义。
     * 直接new Gson()会对字符串中的一些字符进行转义的，如等于号会被转义为：\u003d
     */
    private val GSON = GsonBuilder().disableHtmlEscaping().create()
    private val uiHandler = Handler(Looper.getMainLooper())

    var defaultOkHttpClient: OkHttpClient = OkHttpClient.Builder()
        .callTimeout(0, TimeUnit.SECONDS)
        .connectTimeout(3, TimeUnit.SECONDS)
        .readTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .build()

    /** 发送网络请求 */
    private fun <T> sendRequest(okHttpClient: OkHttpClient? = null, request: Request, responseHandler: ((response: Response, callback: ApiRequestCallback<T>) -> SyncRequestResult<T>)? = null, callback: ApiRequestCallback<T>) {
        val okHttpClient = okHttpClient ?: this.defaultOkHttpClient
        val responseCallback = object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                uiHandler.post { callback.onApiRequestFail(getFailureDesc(e, request.url), null) }
            }

            override fun onResponse(call: Call, response: Response) {
                response.use {
                    val processResponse = responseHandler ?: ::processResponse
                    processResponse(response, callback)
                }
            }
        }
        okHttpClient.newCall(request).enqueue(responseCallback)
    }

    private fun <T> processResponse(response: Response, callback: ApiRequestCallback<T>): SyncRequestResult<T> {
        val url = response.request.url
        if (!response.isSuccessful) {
            val responseCode = response.code
            var body = ""
            try { body = response.body.string() } catch (e: Exception) { }
            val baseResult = fromJson(body, RequestResult::class.java) ?: RequestResult<Any>()
            baseResult.responseCode = responseCode

            Timber.v("响应体：$body")
            Timber.w("OkHttp请求出现异常, responseCode: ${responseCode}，responseBody：$body，url = $url")
            val failCause = "$responseCode 服务器忙，请稍后再试。"
            uiHandler.post { callback.onApiRequestFail(failCause, baseResult) }
            return SyncRequestResult(null, baseResult, failCause)
        }

        var body: String
        try {
            body = response.body.string()
            Timber.v("响应体：$body")
        } catch (e: Exception) {
            val failCause = getFailureDesc(e, url)
            uiHandler.post { callback.onApiRequestFail(failCause, null) }
            return SyncRequestResult(null, null, failCause)
        }

        if (TextUtils.isEmpty(body)) {
            Timber.w("OkHttp请求出现异常，服务器返回空值, url = $url")
            val failCause = "-100 服务器忙，请稍后再试。"
            uiHandler.post { callback.onApiRequestFail(failCause, null) }
            return SyncRequestResult(null, null, failCause)
        }

        //var type: Type = callback.type // 拿到ApiRequestCallback<T>中泛型T的真实类型(比如LoginBean)，java用Type表示类型，Type有很多不同的子类实现
        // 如果T的真实类型不带泛型(比如：Person)，则type的实现就是一个Class，如果带泛型（比如：List<Person>），则type是ParameterizedType接口的实现类实现的，

        // 如果对于返回类型有特殊需求，可以让你的JavaBean实现特定的接口，比如实现`IRequestResult`接口，表示要把返回结果封装成RequestResult<YouBean>
        // 得到一个类实现的接口：class.getInterfaces()

        val type: Type = callback.type
        var needRequestResult = false
        if (type is Class<*>) {
            // ApiRequestCallback<Department> 中的泛型参数Department是一个Class
            needRequestResult = type.interfaces.any { it == IRequestResult::class.java }
        } else if (type is ParameterizedType){
            // // ApiRequestCallback<List<Department>> 中的泛型参数List<Department>是一个ParameterizedType
            val rawType = type.rawType // 拿到List<Department>中的List，它是一个Class
            if (rawType is Class<*>) {
                needRequestResult = rawType.interfaces.any { it == IRequestResult::class.java }
            }
        }

        return if (needRequestResult) {
            // 将json解析为RequestResult<T>
            parseRequestResult(response, body, callback)
        } else {
            // 将json解析为T
            parse(url, body, callback)
        }
    }

    /**
     * 把类型封装成RequestResult<T>再解析Json，如果是异步请求，最终返回T，如果是同步请求返回SyncRequestResult<T>对象，里面包含有T
     */
    private fun <T> parseRequestResult(response: Response, json: String, callback: ApiRequestCallback<T>): SyncRequestResult<T> {
        val url = response.request.url
        val responseCode = response.code
        val rawType = RequestResult::class.java
        val actualTypeArguments = callback.type // 得到ApiRequestCallback<LoginBean>中的LoginBean，我们想要包装成这个类型：RequestResult<LoginBean>
        val ownerType: Type? = null
        // 这个parameterizedType就是RequestResult<LoginBean>这样的类型
        val parameterizedType = GsonTypes.newParameterizedTypeWithOwner(ownerType, rawType, actualTypeArguments)
        val requestResult: RequestResult<T>? = fromJson(json, parameterizedType)
        requestResult?.responseCode = responseCode

        if (requestResult == null) {
            Timber.w("请求失败：json无法解析为$parameterizedType，json = $json, url = $url")
            val failCause = "服务器返回的数据无法解析，请稍后再试"
            uiHandler.post { callback.onApiRequestFail(failCause, null) }
            return SyncRequestResult(null, null, failCause)
        }

        // 示例json: {"msg":"查询成功","success":true,"obj":{"id":100,"status":"1"}}

        val success: Boolean? = requestResult.success
        val obj: T? = requestResult.obj

        if (success == null) {
            Timber.w("请求失败：success字段为null，json = $json, url = $url")
            val failCause = "-101 服务器忙，请稍后再试。"
            uiHandler.post { callback.onApiRequestFail(failCause, requestResult) }
            return SyncRequestResult(null, requestResult, failCause)
        }

        if (!success) {
            Timber.w("请求失败, success = false, json = $json, url = $url")
            val failCause = requestResult.msg ?: "请求失败：$json"
            uiHandler.post { callback.onApiRequestFail(failCause, requestResult) }
            return SyncRequestResult(null, requestResult, failCause)
        }

        if (obj == null) {
            Timber.w("请求失败, obj = null, json = $json, url = $url")
            val failCause = "-102 服务器忙，请稍后再试。"
            uiHandler.post { callback.onApiRequestFail(failCause, requestResult) }
            return SyncRequestResult(null, requestResult, failCause)
        }

        uiHandler.post { callback.onApiRequestSuccess(obj, requestResult) }
        return SyncRequestResult(obj, requestResult, null)
    }

    /**
     * 把json解析为T，如果是异步请求，最终返回T，如果是同步请求返回SyncRequestResult<T>对象，里面包含有T
     */
    private fun <T> parse(url: HttpUrl, json: String, callback: ApiRequestCallback<T>): SyncRequestResult<T> {
        val requestResult: T? = fromJson(json, callback.type)
        return if (requestResult == null) {
            Timber.w("请求失败：json无法解析为${callback.type}，json = $json, url = $url")
            val failCause = "服务器返回的数据无法解析，请稍后再试"
            uiHandler.post { callback.onApiRequestFail(failCause, null) }
            SyncRequestResult(null, null, failCause)
        } else {
            uiHandler.post { callback.onApiRequestSuccess(requestResult, null) }
            SyncRequestResult(requestResult, null, null)
        }
    }

    /** 获取失败的原因描述 */
    private fun getFailureDesc(e: Exception, url: HttpUrl): String {
        // w级别会被写到日志，为了预防信息太多，所以w级别不打印详细的错误堆栈信息，而v级别只会在Debug模式下打印日志，所以可以输出详细的堆栈信息。
        Timber.w("OkHttp请求出现异常(${e.javaClass.simpleName}: ${e.message}), url = $url")
        Timber.v(e, "OkHttp请求出现异常, url = $url")
        val failCause = if (e is SocketTimeoutException || (e is InterruptedIOException && e.message == "timeout")) "请求超时，请稍后再试。" else "网络异常，${e.javaClass.simpleName}: ${e.message}"
        return failCause
    }

    /** 请求构造器，使用三步曲：1、指定请求方式和url，2、添加请求参数，3、执行go函数开始请求 */
    class Builder {
        private val requestBuilder = Request.Builder()
        private val params = mutableMapOf<String, String>()
        private var method: String = "GET"
        private lateinit var url: HttpUrl
        private var jsonBody: RequestBody? = null
        /** 上传文件时用到的请求体构建器 */
        private var multipartBodyBuilder: MultipartBody.Builder? = null

        /** get请求 */
        fun get(url: String) = apply {
            this.url = url.toHttpUrlOrNull()!!
            method = "GET"
        }

        /** post请求 */
        fun post(url: String) = apply {
            this.url = url.toHttpUrlOrNull()!!
            method = "POST"
        }

        /** 提交Json的请求，把javaBean传进来会自动转换为json传给服务器 */
        fun json(url: String, javaBean: Any) = apply {
            this.url = url.toHttpUrlOrNull()!!
            jsonBody = toJson(javaBean)!!.toRequestBody("application/json; charset=utf-8".toMediaType())
        }

        /** 上传文件 */
        fun uploadFile(url: String) = apply {
            post(url)
            multipartBodyBuilder = MultipartBody.Builder().setType(MultipartBody.FORM)
        }

        /** 添加上传文件的参数（普通的String参数） */
        fun addUploadFileParam(key: String, value: String) = apply {
            multipartBodyBuilder?.apply { addFormDataPart(key, value)}
        }

        /**
         * 添加上传文件的参数（指定要上传的文件）
         * @param key
         * @param value 指定要上传的文件
         * @param mime 指定要上传的文件类型
         * @param fileName 指定上传文件的名称，可以不传，默认为文件名
         */
        fun addUploadFileParam(key: String, value: File, mime: String, fileName: String = value.name) = apply {
            multipartBodyBuilder?.apply { addFormDataPart(key, fileName, value.asRequestBody(mime.toMediaTypeOrNull())) }
        }

        /** 添加请求头 */
        fun addHeader(name: String, value: String) = apply {
            requestBuilder.addHeader(name, value)
        }

        /** 添加请求参数 */
        fun addParam(key: String, value: String) = apply {
            params[key] = value
        }

        /**
         * 执行请求（异步执行）
         * @param okHttpClient 可以为null，如需要自定义OkHttpClient，则传入自定义的OkHttpClient
         * @param responseHandler 可以为null，如需要自定义处理请求结果，则传入自定义的responseHandler，默认处理器会把接收回调到UI线程，如果使用自定义处理器，则需注意线程问题。
         * @param callback 用于接收请求结
         */
        fun <T> go(callback: ApiRequestCallback<T>, okHttpClient: OkHttpClient? = null, responseHandler: ((response: Response, callback: ApiRequestCallback<T>) -> SyncRequestResult<T>)? = null) {
            val request = createRequest()
            sendRequest(okHttpClient, request, responseHandler, callback)
        }

        /**
         * 执行请求（异步执行）
         * @param responseCallback 响应回调，用于处理响应结果
         * @param okHttpClient 可以为null，如需要自定义OkHttpClient，则传入自定义的OkHttpClient
         */
        fun <T> go(responseCallback: Callback, okHttpClient: OkHttpClient? = null) {
            val request = createRequest()
            val okHttpClient = okHttpClient ?: defaultOkHttpClient
            okHttpClient.newCall(request).enqueue(responseCallback)
        }

        fun <T> goSync(typeToken: TypeToken<T>, okHttpClient: OkHttpClient? = null, responseHandler: ((response: Response, callback: ApiRequestCallback<T>) -> SyncRequestResult<T>)? = null): SyncRequestResult<T> {
            val request = createRequest()
            val okHttpClient = okHttpClient ?: defaultOkHttpClient
            return try {
                okHttpClient.newCall(request).execute().use { response ->
                    val processResponse = responseHandler ?: ::processResponse
                    val result: SyncRequestResult<T> = processResponse(response, typeToken)
                    result
                }
            } catch (e: Exception) {
                SyncRequestResult(null, null, getFailureDesc(e, request.url))
            }
        }

        fun goString(callback: ApiRequestCallback<String>, okHttpClient: OkHttpClient? = null) {
            val request = createRequest()
            val okHttpClient = okHttpClient ?: defaultOkHttpClient
            okHttpClient.newCall(request).enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {
                    uiHandler.post { callback.onApiRequestFail(getFailureDesc(e, request.url), null) }
                }

                override fun onResponse(call: Call, response: Response) = response.use {
                    try {
                        val string = response.body.string()
                        Timber.v("响应体：$string")
                        uiHandler.post { callback.onApiRequestSuccess(string, null) }
                    } catch (e: Exception) {
                        uiHandler.post { callback.onApiRequestFail(getFailureDesc(e, request.url), null) }
                    }
                    Unit
                }
            })
        }

        fun goStringSync(okHttpClient: OkHttpClient? = null): SyncRequestResult<String> {
            val request = createRequest()
            val okHttpClient = okHttpClient ?: defaultOkHttpClient
            return try {
                okHttpClient.newCall(request).execute().use { response ->
                    try {
                        val string = response.body.string()
                        Timber.v("响应体：$string")
                        SyncRequestResult(string, null, null)
                    } catch (e: Exception) {
                        SyncRequestResult(null, null, getFailureDesc(e, request.url))
                    }
                }
            } catch (e: Exception) {
                SyncRequestResult(null, null, getFailureDesc(e, request.url))
            }
        }

        private fun createRequest(): Request {
            when {
                jsonBody != null -> {
                    requestBuilder.url(url).post(jsonBody!!)
                }
                multipartBodyBuilder != null -> {
                    val requestBody = multipartBodyBuilder!!.build()
                    requestBuilder.url(url).post(requestBody)
                }
                method == "GET" -> {
                    val newUrl = url.newBuilder()
                        .apply { params.forEach { (key, value) -> addQueryParameter(key, value) } }
                        .build()
                    requestBuilder.url(newUrl).get()
                }
                method == "POST" -> {
                    val requestBody = FormBody.Builder()
                        .apply { params.forEach { (key, value) -> add(key, value) } }.build()
                    requestBuilder.url(url).post(requestBody)
                }
            }
            return requestBuilder.build()
        }

    }

    /** 把指定的对象转换为String Json */
    fun toJson(src: Any): String {
        var json = ""
        try {
            json = GSON.toJson(src)
        } catch (e: Exception) {
            Timber.w(e, "指定的对象不能转换为json，该对象为：$src")
        }
        return json
    }

    /**
     * 把json字符串转换为指定的对象
     * @param json json字符串
     * @param clazz JavaBean的Class
     * @return
     */
    fun <T> fromJson(json: String, clazz: Class<T>): T? {
        var bean: T? = null
        try {
            bean = GSON.fromJson<T>(json, clazz)
        } catch (e: Exception) {
            Timber.w(e, "解析json数据时出现异常\njson = $json")
        }
        return bean
    }


    /**
     * 把json字符串转换为指定的类型
     * @param json json字符串
     * @param type 要转换的对象类型，比如要转换为集合，则传：val type = object : TypeToken<ArrayList<String>>() {}.type
     * @return
     */
    fun <T> fromJson(json: String, type: Type): T? {
        var bean: T? = null
        try {
            bean = GSON.fromJson<T>(json, type)
        } catch (e: Exception) {
            Timber.w(e,"解析json数据时出现异常\njson = $json")
        }
        return bean
    }

}